package p.ithorns.sample.tracer.jaeger;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ObjectNode;
import io.opentracing.Span;
import io.opentracing.SpanContext;
import io.opentracing.Tracer;
import io.opentracing.util.GlobalTracer;
import org.apache.logging.log4j.util.Strings;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.util.ContentCachingRequestWrapper;
import org.springframework.web.util.ContentCachingResponseWrapper;

import javax.servlet.*;
import javax.servlet.annotation.WebFilter;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.UnsupportedEncodingException;

/**
 * TraceFilter
 *
 * @author HtL
 * @date 2025/3/21 11:02
 * @since 1.0.0
 */
// 使用@Bean注册 这里就不用 @WebFilter(urlPatterns = "/*")
public class TraceFilter implements Filter {

    private final static Logger log = LoggerFactory.getLogger(TraceFilter.class);

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
            throws IOException, ServletException {
        ContentCachingRequestWrapper req = new ContentCachingRequestWrapper((HttpServletRequest) request);
        ContentCachingResponseWrapper resp = new ContentCachingResponseWrapper((HttpServletResponse) response);

        chain.doFilter(req, resp);

        // 如果服务涉及HTML等业务，会出现JSON出错的情况。
        // 所以可以考虑方案二将request、response的String分别打log。

        // 记录日志方案一
        // log2Jackson(req, resp);

        // 记录日志方案二
        logTraceSpan(req, resp);
        // 注：以上两个方案任选其一即可

        // 回写响应数据
        resp.copyBodyToResponse();
    }

    /**
     * 方案二
     * request和response分别作为单独的span整合到日志中，截断超长文本
     * 由于使用Thrift协议将数据发送给Jaeger，存在长度限制，
     * 所以在Response或Request非常长的时候会发生错误导致日志无法被记录，因此需要对超长文本进行截断。
     */
    private void logTraceSpan(ContentCachingRequestWrapper req,
                              ContentCachingResponseWrapper resp) throws UnsupportedEncodingException {
        Tracer tracer = GlobalTracer.get();
        Span span = tracer.activeSpan();
        if (span != null) {
            SpanContext context = span.context();
            String traceId = context.toTraceId();

            // 设置traceId到MDC上下文
            TraceContext.set(traceId);

            String opName = req.getRequestURI();
            span.setOperationName(opName);

            String requestContent = new String(req.getContentAsByteArray(), req.getCharacterEncoding());
            GlobalTracer.get().buildSpan("request")
                    .addReference("", context)
                    .start()
                    .log(Strings.left(requestContent, 10240))
                    .finish();

            String responseContent = new String(resp.getContentAsByteArray(), resp.getCharacterEncoding());
            GlobalTracer.get().buildSpan("response")
                    .addReference("", context)
                    .start()
                    .log(Strings.left(responseContent, 10240))
                    .finish();
        }
    }

    /**
     * 方案一
     * request和response整合到同一条日志中，截断超长文本
     * 如果服务涉及HTML等业务，会出现JSON出错的情况。
     */
    private void logMergeJson(ContentCachingRequestWrapper req,
                              ContentCachingResponseWrapper resp) throws IOException {
        ObjectMapper mapper = new ObjectMapper();
        ObjectNode root = mapper.createObjectNode();
        root.putIfAbsent("request", mapper.readTree(req.getContentAsByteArray()));
        root.putIfAbsent("response", mapper.readTree(resp.getContentAsByteArray()));
//        // 利用Jackson将请求响应数据整合到同一条json结构中，截断超长文本
        log.info(Strings.left(mapper.writeValueAsString(root), 10240));
    }

}